import {Matrix4} from '../math/Matrix4.js'

const _offsetMatrix = new Matrix4()
const _identityMatrix = new Matrix4()

function Skeleton(bones, boneInverses) {
  // copy the bone array

  bones = bones || []

  this.bones = bones.slice(0)
  this.boneMatrices = new Float32Array(this.bones.length * 16)

  this.frame = -1

  // use the supplied bone inverses or calculate the inverses

  if (boneInverses === undefined) {
    this.calculateInverses()
  } else {
    if (this.bones.length === boneInverses.length) {
      this.boneInverses = boneInverses.slice(0)
    } else {
      console.warn('THREE.Skeleton boneInverses is the wrong length.')

      this.boneInverses = []

      for (let i = 0, il = this.bones.length; i < il; i++) {
        this.boneInverses.push(new Matrix4())
      }
    }
  }
}

Object.assign(Skeleton.prototype, {
  calculateInverses: function () {
    this.boneInverses = []

    for (let i = 0, il = this.bones.length; i < il; i++) {
      const inverse = new Matrix4()

      if (this.bones[i]) {
        inverse.getInverse(this.bones[i].matrixWorld)
      }

      this.boneInverses.push(inverse)
    }
  },

  pose: function () {
    // recover the bind-time world matrices

    for (let i = 0, il = this.bones.length; i < il; i++) {
      const bone = this.bones[i]

      if (bone) {
        bone.matrixWorld.getInverse(this.boneInverses[i])
      }
    }

    // compute the local matrices, positions, rotations and scales

    for (let i = 0, il = this.bones.length; i < il; i++) {
      const bone = this.bones[i]

      if (bone) {
        if (bone.parent && bone.parent.isBone) {
          bone.matrix.getInverse(bone.parent.matrixWorld)
          bone.matrix.multiply(bone.matrixWorld)
        } else {
          bone.matrix.copy(bone.matrixWorld)
        }

        bone.matrix.decompose(bone.position, bone.quaternion, bone.scale)
      }
    }
  },

  update: function () {
    const bones = this.bones
    const boneInverses = this.boneInverses
    const boneMatrices = this.boneMatrices
    const boneTexture = this.boneTexture

    // flatten bone matrices to array

    for (let i = 0, il = bones.length; i < il; i++) {
      // compute the offset between the current and the original transform

      const matrix = bones[i] ? bones[i].matrixWorld : _identityMatrix

      _offsetMatrix.multiplyMatrices(matrix, boneInverses[i])
      _offsetMatrix.toArray(boneMatrices, i * 16)
    }

    if (boneTexture !== undefined) {
      boneTexture.needsUpdate = true
    }
  },

  clone: function () {
    return new Skeleton(this.bones, this.boneInverses)
  },

  getBoneByName: function (name) {
    for (let i = 0, il = this.bones.length; i < il; i++) {
      const bone = this.bones[i]

      if (bone.name === name) {
        return bone
      }
    }

    return undefined
  },

  dispose: function () {
    if (this.boneTexture) {
      this.boneTexture.dispose()

      this.boneTexture = undefined
    }
  },
})

export {Skeleton}
